home *** CD-ROM | disk | FTP | other *** search
- /*
- * Copyright (c) 1983 Regents of the University of California.
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- * 1. Redistributions of source code must retain the above copyright
- * notice, this list of conditions and the following disclaimer.
- * 2. Redistributions in binary form must reproduce the above copyright
- * notice, this list of conditions and the following disclaimer in the
- * documentation and/or other materials provided with the distribution.
- * 3. All advertising materials mentioning features or use of this software
- * must display the following acknowledgement:
- * This product includes software developed by the University of
- * California, Berkeley and its contributors.
- * 4. Neither the name of the University nor the names of its contributors
- * may be used to endorse or promote products derived from this software
- * without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
- * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
- * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
- * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
- * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- * SUCH DAMAGE.
- */
- #ifndef lint
- static char RCSid[] =
- "$Id: server.c,v 6.72 1994/04/26 16:58:53 mcooper Exp $";
-
- static char sccsid[] = "@(#)server.c 5.3 (Berkeley) 6/7/86";
-
- static char copyright[] =
- "@(#) Copyright (c) 1983 Regents of the University of California.\n\
- All rights reserved.\n";
- #endif /* not lint */
-
- /*
- * Server routines
- */
-
- #include "defs.h"
-
- char tempname[sizeof _RDIST_TMP + 1]; /* Tmp file name */
- char buf[BUFSIZ]; /* general purpose buffer */
- char target[MAXPATHLEN]; /* target/source directory name */
- char *ptarget; /* pointer to end of target name */
- int catname = 0; /* cat name to target name */
- char *sptarget[32]; /* stack of saved ptarget's for directories */
- char *fromhost = NULL; /* Client hostname */
- static long min_freespace = 0; /* Minimium free space on a filesystem */
- static long min_freefiles = 0; /* Minimium free # files on a filesystem */
- int oumask; /* Old umask */
-
- /*
- * Cat "string" onto the target buffer with error checking.
- */
- static int cattarget(string)
- char *string;
- {
- if (strlen(string) + strlen(target) + 2 > sizeof(target)) {
- message(MT_INFO, "target buffer is not large enough.");
- return(-1);
- }
- if (!ptarget) {
- message(MT_INFO, "NULL target pointer set.");
- return(-10);
- }
-
- (void) sprintf(ptarget, "/%s", string);
-
- return(0);
- }
-
- /*
- * Set uid and gid ownership of a file.
- */
- static int setownership(file, fd, uid, gid)
- char *file;
- int fd;
- UID_T uid;
- GID_T gid;
- {
- int status = -1;
-
- /*
- * We assume only the Superuser can change uid ownership.
- */
- if (getuid() == 0) {
- #if defined(HAVE_FCHOWN)
- if (fd != -1)
- status = fchown(fd, (CHOWN_UID_T) uid,
- (CHOWN_GID_T) gid);
- #endif
- if (status < 0)
- status = chown(file, (CHOWN_UID_T) uid,
- (CHOWN_GID_T) gid);
-
- if (status < 0) {
- message(MT_NOTICE, "%s: chown %d.%d failed: %s",
- target, uid, gid, SYSERR);
- return(-1);
- }
- } else {
- #if defined(HAVE_FCHOWN)
- if (fd != -1)
- status = fchown(fd, (CHOWN_UID_T) -1,
- (CHOWN_GID_T) gid);
- #endif
- if (status < 0)
- status = chown(file, (CHOWN_UID_T) -1,
- (CHOWN_GID_T) gid);
-
- if (status < 0) {
- message(MT_NOTICE, "%s: chgrp %d failed: %s",
- target, gid, SYSERR);
- return(-1);
- }
- }
-
- return(0);
- }
-
- /*
- * Set mode of a file
- */
- static int setmode(file, fd, mode)
- char *file;
- int fd;
- int mode;
- {
- int status = -1;
-
- if (mode == -1)
- return(0);
-
- #if defined(HAVE_FCHMOD)
- if (fd != -1)
- status = fchmod(fd, mode);
- #endif
-
- if (status < 0)
- status = chmod(file, mode);
-
- if (status < 0) {
- message(MT_NOTICE, "%s: chmod failed: %s", target, SYSERR);
- return(-1);
- }
-
- return(0);
- }
- /*
- * Change owner, group and mode of file.
- */
- static int fchog(fd, file, owner, group, mode)
- int fd;
- char *file, *owner, *group;
- int mode;
- {
- static struct group *gr = NULL;
- extern char *locuser;
- register int i;
- UID_T uid;
- GID_T gid;
- GID_T primegid = (GID_T)-2;
-
- uid = userid;
- if (userid == 0) { /* running as root; take anything */
- if (*owner == ':') {
- uid = (UID_T) atoi(owner + 1);
- } else if (pw == NULL || strcmp(owner, pw->pw_name) != 0) {
- if ((pw = getpwnam(owner)) == NULL) {
- if (mode != -1 && IS_ON(mode, S_ISUID)) {
- message(MT_NOTICE,
- "%s: unknown login name \"%s\", clearing setuid",
- target, owner);
- mode &= ~S_ISUID;
- uid = 0;
- } else
- message(MT_NOTICE,
- "%s: unknown login name \"%s\"",
- target, owner);
- } else
- uid = pw->pw_uid;
- } else {
- uid = pw->pw_uid;
- primegid = pw->pw_gid;
- }
- if (*group == ':') {
- gid = (GID_T) atoi(group + 1);
- goto ok;
- }
- } else { /* not root, setuid only if user==owner */
- struct passwd *lupw;
-
- if (mode != -1) {
- if (IS_ON(mode, S_ISUID) &&
- strcmp(locuser, owner) != 0)
- mode &= ~S_ISUID;
- if (mode)
- mode &= ~S_ISVTX; /* and strip sticky too */
- }
-
- if ((lupw = getpwnam(locuser)) != NULL)
- primegid = lupw->pw_gid;
- }
-
- gid = (GID_T) -1;
- if (gr == NULL || strcmp(group, gr->gr_name) != 0) {
- if ((*group == ':' &&
- (getgrgid(gid = atoi(group + 1)) == NULL))
- || ((gr = (struct group *)getgrnam(group)) == NULL)) {
- if (mode != -1 && IS_ON(mode, S_ISGID)) {
- message(MT_NOTICE,
- "%s: unknown group \"%s\", clearing setgid",
- target, group);
- mode &= ~S_ISGID;
- } else
- message(MT_NOTICE,
- "%s: unknown group \"%s\"",
- target, group);
- } else
- gid = gr->gr_gid;
- } else
- gid = gr->gr_gid;
-
- if (userid && gid >= 0x7fff && gid != primegid) {
- if (gr)
- for (i = 0; gr->gr_mem[i] != NULL; i++)
- if (strcmp(locuser, gr->gr_mem[i]) == 0)
- goto ok;
- if (mode != -1 && IS_ON(mode, S_ISGID)) {
- message(MT_NOTICE,
- "%s: user %s not in group %s, clearing setgid",
- target, locuser, group);
- mode &= ~S_ISGID;
- }
- gid = -1;
- }
- ok:
- /*
- * Set uid and gid ownership. If that fails, strip setuid and
- * setgid bits from mode. Once ownership is set, successful
- * or otherwise, set the new file mode.
- */
- if (setownership(file, fd, uid, gid) < 0) {
- if (mode != -1 && IS_ON(mode, S_ISUID)) {
- message(MT_NOTICE,
- "%s: chown failed, clearing setuid", target);
- mode &= ~S_ISUID;
- }
- if (mode != -1 && IS_ON(mode, S_ISGID)) {
- message(MT_NOTICE,
- "%s: chown failed, clearing setgid", target);
- mode &= ~S_ISGID;
- }
- }
- (void) setmode(file, fd, mode);
-
-
- return(0);
- }
-
- /*
- * Remove a file or directory (recursively) and send back an acknowledge
- * or an error message.
- */
- static int removefile(statb)
- struct stat *statb;
- {
- DIR *d;
- static DIRENTRY *dp;
- register char *cp;
- struct stat stb;
- char *optarget;
- int len, failures = 0;
-
- switch (statb->st_mode & S_IFMT) {
- case S_IFREG:
- case S_IFLNK:
- if (unlink(target) < 0) {
- if (errno == ETXTBSY) {
- message(MT_REMOTE|MT_NOTICE,
- "%s: unlink failed: %s",
- target, SYSERR);
- return(0);
- } else {
- error("%s: unlink failed: %s", target, SYSERR);
- return(-1);
- }
- }
- goto removed;
-
- case S_IFDIR:
- break;
-
- default:
- error("%s: not a plain file", target);
- return(-1);
- }
-
- errno = 0;
- if ((d = opendir(target)) == NULL) {
- error("%s: opendir failed: %s", target, SYSERR);
- return(-1);
- }
-
- optarget = ptarget;
- len = ptarget - target;
- while (dp = readdir(d)) {
- if ((D_NAMLEN(dp) == 1 && dp->d_name[0] == '.') ||
- (D_NAMLEN(dp) == 2 && dp->d_name[0] == '.' &&
- dp->d_name[1] == '.'))
- continue;
-
- if (len + 1 + (int)strlen(dp->d_name) >= MAXPATHLEN - 1) {
- message(MT_REMOTE|MT_WARNING, "%s/%s: Name too long",
- target, dp->d_name);
- continue;
- }
- ptarget = optarget;
- *ptarget++ = '/';
- cp = dp->d_name;;
- while (*ptarget++ = *cp++)
- ;
- ptarget--;
- if (lstat(target, &stb) < 0) {
- message(MT_REMOTE|MT_WARNING, "%s: lstat failed: %s",
- target, SYSERR);
- continue;
- }
- if (removefile(&stb) < 0)
- ++failures;
- }
- (void) closedir(d);
- ptarget = optarget;
- *ptarget = CNULL;
-
- if (failures)
- return(-1);
-
- if (rmdir(target) < 0) {
- error("%s: rmdir failed: %s", target, SYSERR);
- return(-1);
- }
- removed:
- message(MT_CHANGE|MT_REMOTE, "%s: removed", target);
- return(0);
- }
-
- /*
- * Check the current directory (initialized by the 'T' command to server())
- * for extraneous files and remove them.
- */
- static void doclean(cp)
- register char *cp;
- {
- DIR *d;
- register DIRENTRY *dp;
- struct stat stb;
- char *optarget, *ep;
- int len;
- opt_t opts;
-
- opts = strtol(cp, &ep, 8);
- if (*ep != CNULL) {
- error("clean: options not delimited");
- return;
- }
- if ((d = opendir(target)) == NULL) {
- error("%s: opendir failed: %s", target, SYSERR);
- return;
- }
- ack();
-
- optarget = ptarget;
- len = ptarget - target;
- while (dp = readdir(d)) {
- if ((D_NAMLEN(dp) == 1 && dp->d_name[0] == '.') ||
- (D_NAMLEN(dp) == 2 && dp->d_name[0] == '.' &&
- dp->d_name[1] == '.'))
- continue;
-
- if (len + 1 + (int)strlen(dp->d_name) >= MAXPATHLEN - 1) {
- message(MT_REMOTE|MT_WARNING, "%s/%s: Name too long",
- target, dp->d_name);
- continue;
- }
- ptarget = optarget;
- *ptarget++ = '/';
- cp = dp->d_name;;
- while (*ptarget++ = *cp++)
- ;
- ptarget--;
- if (lstat(target, &stb) < 0) {
- message(MT_REMOTE|MT_WARNING, "%s: lstat failed: %s",
- target, SYSERR);
- continue;
- }
-
- (void) sendcmd(CC_QUERY, "%s", dp->d_name);
- (void) remline(cp = buf, sizeof(buf), TRUE);
-
- if (*cp != CC_YES)
- continue;
-
- if (IS_ON(opts, DO_VERIFY))
- message(MT_REMOTE|MT_INFO, "%s: need to remove",
- target);
- else
- (void) removefile(&stb);
- }
- (void) closedir(d);
-
- ptarget = optarget;
- *ptarget = CNULL;
- }
-
- /*
- * Frontend to doclean().
- */
- static void clean(cp)
- register char *cp;
- {
- doclean(cp);
- (void) sendcmd(CC_END, NULL);
- (void) response();
- }
-
- /*
- * Execute a shell command to handle special cases.
- * We can't really set an alarm timeout here since we
- * have no idea how long the command should take.
- */
- static void dospecial(cmd)
- char *cmd;
- {
- runcommand(cmd);
- }
-
- /*
- * Do a special cmd command. This differs from normal special
- * commands in that it's done after an entire command has been updated.
- * The list of updated target files is sent one at a time with RC_FILE
- * commands. Each one is added to an environment variable defined by
- * E_FILES. When an RC_COMMAND is finally received, the E_FILES variable
- * is stuffed into our environment and a normal dospecial() command is run.
- */
- static void docmdspecial()
- {
- register char *cp;
- char *cmd, *env = NULL;
- int n;
- int len;
-
- /* We're ready */
- ack();
-
- for ( ; ; ) {
- n = remline(cp = buf, sizeof(buf), FALSE);
- if (n <= 0) {
- error("cmdspecial: premature end of input.");
- return;
- }
-
- switch (*cp++) {
- case RC_FILE:
- if (env == NULL) {
- len = (2 * sizeof(E_FILES)) + strlen(cp) + 10;
- env = (char *) xmalloc(len);
- (void) sprintf(env, "export %s;%s=%s",
- E_FILES, E_FILES, cp);
- } else {
- len = strlen(env);
- env = (char *) xrealloc(env,
- len + strlen(cp) + 2);
- env[len] = CNULL;
- (void) strcat(env, ":");
- (void) strcat(env, cp);
- }
- ack();
- break;
-
- case RC_COMMAND:
- if (env) {
- len = strlen(env);
- env = (char *) xrealloc(env,
- len + strlen(cp) + 2);
- env[len] = CNULL;
- (void) strcat(env, ";");
- (void) strcat(env, cp);
- cmd = env;
- } else
- cmd = cp;
-
- dospecial(cmd);
- if (env)
- (void) free(env);
- return;
-
- default:
- error("Unknown cmdspecial command '%s'.", cp);
- return;
- }
- }
- }
-
- /*
- * Query. Check to see if file exists. Return one of the following:
- *
- #ifdef NFS_CHECK
- * QC_ONNFS - resides on a NFS
- #endif NFS_CHECK
- #ifdef RO_CHECK
- * QC_ONRO - resides on a Read-Only filesystem
- #endif RO_CHECK
- * QC_NO - doesn't exist
- * QC_YESsize mtime - exists and its a regular file (size & mtime of file)
- * QC_YES - exists and its a directory or symbolic link
- * QC_ERRMSGmessage - error message
- */
- static void query(name)
- char *name;
- {
- static struct stat stb;
- int s = -1, stbvalid = 0;
-
- if (catname && cattarget(name) < 0)
- return;
-
- #if defined(NFS_CHECK)
- if (IS_ON(options, DO_CHKNFS)) {
- s = is_nfs_mounted(target, &stb, &stbvalid);
- if (s > 0)
- (void) sendcmd(QC_ONNFS, NULL);
-
- /* Either the above check was true or an error occured */
- /* and is_nfs_mounted sent the error message */
- if (s != 0) {
- *ptarget = CNULL;
- return;
- }
- }
- #endif /* NFS_CHECK */
-
- #if defined(RO_CHECK)
- if (IS_ON(options, DO_CHKREADONLY)) {
- s = is_ro_mounted(target, &stb, &stbvalid);
- if (s > 0)
- (void) sendcmd(QC_ONRO, NULL);
-
- /* Either the above check was true or an error occured */
- /* and is_ro_mounted sent the error message */
- if (s != 0) {
- *ptarget = CNULL;
- return;
- }
- }
- #endif /* RO_CHECK */
-
- if (IS_ON(options, DO_CHKSYM)) {
- if (is_symlinked(target, &stb, &stbvalid) > 0) {
- (void) sendcmd(QC_SYM, NULL);
- return;
- }
- }
-
- /*
- * If stbvalid is false, "stb" is not valid because:
- * a) RO_CHECK and NFS_CHECK were not defined
- * b) The stat by is_*_mounted() either failed or
- * does not match "target".
- */
- if (!stbvalid && lstat(target, &stb) < 0) {
- if (errno == ENOENT)
- (void) sendcmd(QC_NO, NULL);
- else
- error("%s: lstat failed: %s", target, SYSERR);
- *ptarget = CNULL;
- return;
- }
-
- switch (stb.st_mode & S_IFMT) {
- case S_IFLNK:
- case S_IFDIR:
- case S_IFREG:
- (void) sendcmd(QC_YES, "%ld %ld %o %s %s",
- stb.st_size, stb.st_mtime, stb.st_mode & 07777,
- getusername(stb.st_uid, target, options),
- getgroupname(stb.st_gid, target, options));
- break;
-
- default:
- error("%s: not a file or directory", target);
- break;
- }
- *ptarget = CNULL;
- }
-
- /*
- * Check to see if parent directory exists and create one if not.
- */
- static int chkparent(name, opts)
- char *name;
- opt_t opts;
- {
- register char *cp;
- struct stat stb;
- int r = -1;
-
- debugmsg(DM_CALL, "chkparent(%s, %o) start\n", name, opts);
-
- cp = strrchr(name, '/');
- if (cp == NULL || cp == name)
- return(0);
-
- *cp = CNULL;
-
- if (lstat(name, &stb) < 0) {
- if (errno == ENOENT && chkparent(name, opts) >= 0) {
- if (mkdir(name, 0777 & ~oumask) == 0) {
- message(MT_NOTICE, "%s: mkdir", name);
- r = 0;
- } else
- debugmsg(DM_MISC,
- "chkparent(%s, %o) mkdir fail: %s\n",
- name, opts, SYSERR);
- }
- } else /* It exists */
- r = 0;
-
- /* Put back what we took away */
- *cp = '/';
-
- return(r);
- }
-
- /*
- * Save a copy of 'file' by renaming it.
- */
- static char *savetarget(file)
- char *file;
- {
- static char savefile[MAXPATHLEN];
-
- if (strlen(file) + sizeof(SAVE_SUFFIX) + 1 > MAXPATHLEN) {
- error("%s: Cannot save: Save name too long", file);
- return((char *) NULL);
- }
-
- (void) sprintf(savefile, "%s%s", file, SAVE_SUFFIX);
-
- if (unlink(savefile) != 0 && errno != ENOENT) {
- message(MT_NOTICE, "%s: remove failed: %s", savefile, SYSERR);
- return((char *) NULL);
- }
-
- if (rename(file, savefile) != 0 && errno != ENOENT) {
- error("%s -> %s: rename failed: %s",
- file, savefile, SYSERR);
- return((char *) NULL);
- }
-
- return(savefile);
- }
-
- /*
- * Receive a file
- */
- static void recvfile(new, opts, mode, owner, group, mtime, atime, size)
- /*ARGSUSED*/
- char *new;
- opt_t opts;
- int mode;
- char *owner, *group;
- time_t mtime;
- time_t atime;
- off_t size;
- {
- int f, wrerr, olderrno;
- off_t i;
- register char *cp;
- char *savefile = NULL;
-
- /*
- * Create temporary file
- */
- if ((f = creat(new, mode)) < 0) {
- if (errno != ENOENT || chkparent(new, opts) < 0 ||
- (f = creat(new, mode)) < 0) {
- error("%s: create failed: %s", new, SYSERR);
- (void) unlink(new);
- return;
- }
- }
-
- /*
- * Receive the file itself
- */
- ack();
- wrerr = 0;
- olderrno = 0;
- for (i = 0; i < size; i += BUFSIZ) {
- int amt = BUFSIZ;
-
- cp = buf;
- if (i + amt > size)
- amt = size - i;
- do {
- int j;
-
- j = readrem(cp, amt);
- if (j <= 0) {
- (void) close(f);
- (void) unlink(new);
- fatalerr(
- "Read error occured while receiving file.");
- finish();
- }
- amt -= j;
- cp += j;
- } while (amt > 0);
- amt = BUFSIZ;
- if (i + amt > size)
- amt = size - i;
- if (wrerr == 0 && write(f, buf, amt) != amt) {
- olderrno = errno;
- wrerr++;
- }
- }
-
- if (response() < 0) {
- (void) close(f);
- (void) unlink(new);
- return;
- }
- if (wrerr) {
- error("%s: Write error: %s", new, strerror(olderrno));
- (void) close(f);
- (void) unlink(new);
- return;
- }
-
- /*
- * Do file comparison if enabled
- */
- if (IS_ON(opts, DO_COMPARE)) {
- FILE *f1, *f2;
- int c;
-
- errno = 0; /* fopen is not a syscall */
- if ((f1 = fopen(target, "r")) == NULL) {
- error("%s: open for read failed: %s", target, SYSERR);
- (void) close(f);
- (void) unlink(new);
- return;
- }
- errno = 0;
- if ((f2 = fopen(new, "r")) == NULL) {
- error("%s: open for read failed: %s", new, SYSERR);
- (void) close(f);
- (void) unlink(new);
- return;
- }
- while ((c = getc(f1)) == getc(f2))
- if (c == EOF) {
- debugmsg(DM_MISC,
- "Files are the same '%s' '%s'.",
- target, new);
- (void) fclose(f1);
- (void) fclose(f2);
- (void) close(f);
- (void) unlink(new);
- /*
- * This isn't an error per-se, but we
- * need to indicate to the master that
- * the file was not updated.
- */
- error("");
- return;
- }
- debugmsg(DM_MISC, "Files are different '%s' '%s'.",
- target, new);
- (void) fclose(f1);
- (void) fclose(f2);
- if (IS_ON(opts, DO_VERIFY)) {
- message(MT_REMOTE|MT_INFO, "%s: need to update",
- target);
- (void) close(f);
- (void) unlink(new);
- return;
- }
- }
-
- /*
- * Set owner, group, and file mode
- */
- if (fchog(f, new, owner, group, mode) < 0) {
- (void) close(f);
- (void) unlink(new);
- return;
- }
- (void) close(f);
-
- /*
- * Perform utimes() after file is closed to make
- * certain OS's, such as NeXT 2.1, happy.
- */
- if (setfiletime(new, time((time_t *) 0), mtime) < 0)
- message(MT_NOTICE, "%s: utimes failed: %s", new, SYSERR);
-
- /*
- * Try to save target file from being over-written
- */
- if (IS_ON(opts, DO_SAVETARGETS))
- if ((savefile = savetarget(target)) == NULL) {
- (void) unlink(new);
- return;
- }
-
- /*
- * Install new (temporary) file as the actual target
- */
- if (rename(new, target) < 0) {
- /*
- * If the rename failed due to "Text file busy", then
- * try to rename the target file and retry the rename.
- */
- if (errno == ETXTBSY) {
- /* Save the target */
- if ((savefile = savetarget(target)) != NULL) {
- /* Retry installing new file as target */
- if (rename(new, target) < 0) {
- error("%s -> %s: rename failed: %s",
- new, target, SYSERR);
- /* Try to put back save file */
- if (rename(savefile, target) < 0)
- error(
- "%s -> %s: rename failed: %s",
- savefile, target,
- SYSERR);
- } else
- message(MT_NOTICE, "%s: renamed to %s",
- target, savefile);
- }
- } else {
- error("%s -> %s: rename failed: %s",
- new, target, SYSERR);
- (void) unlink(new);
- }
- }
-
- if (IS_ON(opts, DO_COMPARE))
- message(MT_REMOTE|MT_CHANGE, "%s: updated", target);
- else
- ack();
- }
-
- /*
- * Receive a directory
- */
- static void recvdir(opts, mode, owner, group)
- opt_t opts;
- int mode;
- char *owner, *group;
- {
- static char lowner[100], lgroup[100];
- register char *cp;
- struct stat stb;
- int s;
-
- s = lstat(target, &stb);
- if (s == 0) {
- /*
- * If target is not a directory, remove it
- */
- if (!S_ISDIR(stb.st_mode)) {
- if (IS_ON(opts, DO_VERIFY))
- message(MT_NOTICE, "%s: need to remove",
- target);
- else {
- if (unlink(target) < 0) {
- error("%s: remove failed: %s",
- target, SYSERR);
- return;
- }
- }
- s = -1;
- errno = ENOENT;
- } else {
- if (!IS_ON(opts, DO_NOCHKMODE) &&
- (stb.st_mode & 07777) != mode) {
- if (IS_ON(opts, DO_VERIFY))
- message(MT_NOTICE,
- "%s: need to chmod to %o",
- target, mode);
- else {
- if (chmod(target, mode) != 0)
- message(MT_NOTICE,
- "%s: chmod from %o to %o failed: %s",
- target,
- stb.st_mode & 07777,
- mode,
- SYSERR);
- else
- message(MT_NOTICE,
- "%s: chmod from %o to %o",
- target,
- stb.st_mode & 07777,
- mode);
- }
- }
-
- /*
- * Check ownership and set if necessary
- */
- lowner[0] = CNULL;
- lgroup[0] = CNULL;
-
- if (!IS_ON(opts, DO_NOCHKOWNER) && owner) {
- int o;
-
- o = (owner[0] == ':') ? opts & DO_NUMCHKOWNER :
- opts;
- if (cp = getusername(stb.st_uid, target, o))
- if (strcmp(owner, cp))
- (void) strcpy(lowner, cp);
- }
- if (!IS_ON(opts, DO_NOCHKGROUP) && group) {
- int o;
-
- o = (group[0] == ':') ? opts & DO_NUMCHKGROUP :
- opts;
- if (cp = getgroupname(stb.st_gid, target, o))
- if (strcmp(group, cp))
- (void) strcpy(lgroup, cp);
- }
-
- /*
- * Need to set owner and/or group
- */
- #define PRN(n) ((n[0] == ':') ? n+1 : n)
- if (lowner[0] != CNULL || lgroup[0] != CNULL) {
- if (lowner[0] == CNULL &&
- (cp = getusername(stb.st_uid,
- target, opts)))
- (void) strcpy(lowner, cp);
- if (lgroup[0] == CNULL &&
- (cp = getgroupname(stb.st_gid,
- target, opts)))
- (void) strcpy(lgroup, cp);
-
- if (IS_ON(opts, DO_VERIFY))
- message(MT_NOTICE,
- "%s: need to chown from %s.%s to %s.%s",
- target,
- PRN(lowner), PRN(lgroup),
- PRN(owner), PRN(group));
- else {
- if (fchog(-1, target, owner,
- group, -1) == 0)
- message(MT_NOTICE,
- "%s: chown from %s.%s to %s.%s",
- target,
- PRN(lowner),
- PRN(lgroup),
- PRN(owner),
- PRN(group));
- }
- }
- #undef PRN
- ack();
- return;
- }
- }
-
- if (IS_ON(opts, DO_VERIFY)) {
- ack();
- return;
- }
-
- /*
- * Create the directory
- */
- if (s < 0) {
- if (errno == ENOENT) {
- if (mkdir(target, mode) == 0 ||
- chkparent(target, opts) == 0 &&
- mkdir(target, mode) == 0) {
- message(MT_NOTICE, "%s: mkdir", target);
- (void) fchog(-1, target, owner, group, mode);
- ack();
- } else {
- error("%s: mkdir failed: %s", target, SYSERR);
- ptarget = sptarget[--catname];
- *ptarget = CNULL;
- }
- return;
- }
- }
- error("%s: lstat failed: %s", target, SYSERR);
- ptarget = sptarget[--catname];
- *ptarget = CNULL;
- }
-
- /*
- * Receive a link
- */
- static void recvlink(new, opts, mode, size)
- char *new;
- opt_t opts;
- int mode;
- off_t size;
- {
- struct stat stb;
- char *optarget;
- off_t i;
-
- /*
- * Read basic link info
- */
- ack();
- (void) remline(buf, sizeof(buf), TRUE);
-
- if (response() < 0) {
- err();
- return;
- }
-
- /*
- * Make new symlink using a temporary name
- */
- if (symlink(buf, new) < 0) {
- if (errno != ENOENT || chkparent(new, opts) < 0 ||
- symlink(buf, new) < 0) {
- error("%s -> %s: symlink failed: %s", new, buf,SYSERR);
- (void) unlink(new);
- return;
- }
- }
-
- /*
- * Do comparison of what link is pointing to if enabled
- */
- mode &= 0777;
- if (IS_ON(opts, DO_COMPARE)) {
- char tbuf[MAXPATHLEN];
-
- if ((i = readlink(target, tbuf, sizeof(tbuf))) >= 0 &&
- i == size && strncmp(buf, tbuf, (int) size) == 0) {
- (void) unlink(new);
- ack();
- return;
- }
- if (IS_ON(opts, DO_VERIFY)) {
- (void) unlink(new);
- message(MT_REMOTE|MT_INFO, "%s: need to update",
- target);
- (void) sendcmd(C_END, NULL);
- (void) response();
- return;
- }
- }
-
- /*
- * See if target is a directory and remove it if it is
- */
- if (lstat(target, &stb) == 0) {
- if (S_ISDIR(stb.st_mode)) {
- optarget = ptarget;
- for (ptarget = target; *ptarget; ptarget++);
- if (removefile(&stb) < 0) {
- ptarget = optarget;
- (void) unlink(new);
- (void) sendcmd(C_END, NULL);
- (void) response();
- return;
- }
- ptarget = optarget;
- }
- }
-
- /*
- * Install link as the target
- */
- if (rename(new, target) < 0) {
- error("%s -> %s: symlink rename failed: %s",
- new, target, SYSERR);
- (void) unlink(new);
- (void) sendcmd(C_END, NULL);
- (void) response();
- return;
- }
-
- if (IS_ON(opts, DO_COMPARE))
- message(MT_REMOTE|MT_CHANGE, "%s: updated", target);
- else
- ack();
-
- /*
- * Indicate end of receive operation
- */
- (void) sendcmd(C_END, NULL);
- (void) response();
- }
-
- /*
- * Creat a hard link to existing file.
- */
- static void hardlink(cmd)
- char *cmd;
- {
- struct stat stb;
- int exists = 0;
- char *oldname, *newname;
- char *cp = cmd;
- static char expbuf[BUFSIZ];
-
- /* Skip over opts */
- (void) strtol(cp, &cp, 8);
- if (*cp++ != ' ') {
- error("hardlink: options not delimited");
- return;
- }
-
- oldname = strtok(cp, " ");
- if (oldname == NULL) {
- error("hardlink: oldname name not delimited");
- return;
- }
-
- newname = strtok((char *)NULL, " ");
- if (newname == NULL) {
- error("hardlink: new name not specified");
- return;
- }
-
- if (exptilde(expbuf, oldname) == NULL) {
- error("hardlink: tilde expansion failed");
- return;
- }
- oldname = expbuf;
-
- if (catname && cattarget(newname) < 0) {
- error("Cannot set newname target.");
- return;
- }
-
- if (lstat(target, &stb) == 0) {
- int mode = stb.st_mode & S_IFMT;
-
- if (mode != S_IFREG && mode != S_IFLNK) {
- error("%s: not a regular file", target);
- return;
- }
- exists = 1;
- }
-
- if (chkparent(target, options) < 0 ) {
- error("%s: no parent: %s ", target, SYSERR);
- return;
- }
- if (exists && (unlink(target) < 0)) {
- error("%s: unlink failed: %s", target, SYSERR);
- return;
- }
- if (link(oldname, target) < 0) {
- error("%s: cannot link to %s: %s", target, oldname, SYSERR);
- return;
- }
- ack();
- }
-
- /*
- * Set configuration information.
- *
- * A key letter is followed immediately by the value
- * to set. The keys are:
- * SC_FREESPACE - Set minimium free space of filesystem
- * SC_FREEFILES - Set minimium free number of files of filesystem
- */
- static void setconfig(cmd)
- char *cmd;
- {
- register char *cp = cmd;
- char *estr;
-
- switch (*cp++) {
- case SC_HOSTNAME: /* Set hostname */
- /*
- * Only use info if we don't know who this is.
- */
- if (!fromhost) {
- fromhost = strdup(cp);
- message(MT_SYSLOG, "startup for %s", fromhost);
- #if defined(SETARGS)
- setproctitle("serving %s", cp);
- #endif /* SETARGS */
- }
- break;
-
- case SC_FREESPACE: /* Minimium free space */
- if (!isdigit(*cp)) {
- fatalerr("Expected digit, got '%s'.", cp);
- return;
- }
- min_freespace = (unsigned long) atoi(cp);
- break;
-
- case SC_FREEFILES: /* Minimium free files */
- if (!isdigit(*cp)) {
- fatalerr("Expected digit, got '%s'.", cp);
- return;
- }
- min_freefiles = (unsigned long) atoi(cp);
- break;
-
- case SC_LOGGING: /* Logging options */
- if (estr = msgparseopts(cp, TRUE)) {
- fatalerr("Bad message option string (%s): %s",
- cp, estr);
- return;
- }
- break;
-
- default:
- message(MT_NOTICE, "Unknown config command \"%s\".", cp-1);
- return;
- }
- }
-
- /*
- * Receive something
- */
- static void recvit(cmd, type)
- char *cmd;
- int type;
- {
- int mode;
- opt_t opts;
- off_t size;
- time_t mtime, atime;
- char *owner, *group, *file;
- char new[MAXPATHLEN];
- int freespace = -1, freefiles = -1;
- char *cp = cmd;
-
- /*
- * Get rdist option flags
- */
- opts = strtol(cp, &cp, 8);
- if (*cp++ != ' ') {
- error("recvit: options not delimited");
- return;
- }
-
- /*
- * Get file mode
- */
- mode = strtol(cp, &cp, 8);
- if (*cp++ != ' ') {
- error("recvit: mode not delimited");
- return;
- }
-
- /*
- * Get file size
- */
- size = strtol(cp, &cp, 10);
- if (*cp++ != ' ') {
- error("recvit: size not delimited");
- return;
- }
-
- /*
- * Get modification time
- */
- mtime = strtol(cp, &cp, 10);
- if (*cp++ != ' ') {
- error("recvit: mtime not delimited");
- return;
- }
-
- /*
- * Get access time
- */
- atime = strtol(cp, &cp, 10);
- if (*cp++ != ' ') {
- error("recvit: atime not delimited");
- return;
- }
-
- /*
- * Get file owner name
- */
- owner = strtok(cp, " ");
- if (owner == NULL) {
- error("recvit: owner name not delimited");
- return;
- }
-
- /*
- * Get file group name
- */
- group = strtok((char *)NULL, " ");
- if (group == NULL) {
- error("recvit: group name not delimited");
- return;
- }
-
- /*
- * Get file name. Can't use strtok() since there could
- * be white space in the file name.
- */
- file = group + strlen(group) + 1;
- if (file == NULL) {
- error("recvit: no file name");
- return;
- }
-
- debugmsg(DM_MISC,
- "recvit: opts = %04o mode = %04o size = %d mtime = %d",
- opts, mode, size, mtime);
- debugmsg(DM_MISC,
- "recvit: owner = '%s' group = '%s' file = '%s' catname = %d isdir = %d",
- owner, group, file, catname, (type == S_IFDIR) ? 1 : 0);
-
- if (type == S_IFDIR) {
- if (catname >= sizeof(sptarget)) {
- error("%s: too many directory levels", target);
- return;
- }
- sptarget[catname] = ptarget;
- if (catname++) {
- *ptarget++ = '/';
- while (*ptarget++ = *file++)
- ;
- ptarget--;
- }
- } else {
- /*
- * Create name of temporary file
- */
- if (catname && cattarget(file) < 0) {
- error("Cannot set file name.");
- return;
- }
- file = strrchr(target, '/');
- if (file == NULL)
- (void) strcpy(new, tempname);
- else if (file == target)
- (void) sprintf(new, "/%s", tempname);
- else {
- *file = CNULL;
- (void) sprintf(new, "%s/%s", target, tempname);
- *file = '/';
- }
- (void) mktemp(new);
- }
-
- /*
- * Check to see if there is enough free space and inodes
- * to install this file.
- */
- if (min_freespace || min_freefiles) {
- /* Convert file size to kilobytes */
- int fsize = size / 1024;
-
- if (getfilesysinfo(target, &freespace, &freefiles) != 0)
- return;
-
- /*
- * filesystem values < 0 indicate unsupported or unavailable
- * information.
- */
- if (min_freespace && (freespace >= 0) &&
- (freespace - fsize < min_freespace)) {
- error(
- "%s: Not enough free space on filesystem: min %d free %d",
- target, min_freespace, freespace);
- return;
- }
- if (min_freefiles && (freefiles >= 0) &&
- (freefiles - 1 < min_freefiles)) {
- error(
- "%s: Not enough free files on filesystem: min %d free %d",
- target, min_freefiles, freefiles);
- return;
- }
- }
-
- /*
- * Call appropriate receive function to receive file
- */
- switch (type) {
- case S_IFDIR:
- recvdir(opts, mode, owner, group);
- break;
-
- case S_IFLNK:
- recvlink(new, opts, mode, size);
- break;
-
- case S_IFREG:
- recvfile(new, opts, mode, owner, group, mtime, atime, size);
- break;
-
- default:
- error("%d: unknown file type", type);
- break;
- }
- }
-
- /*
- * Set target information
- */
- static void settarget(cmd, isdir)
- char *cmd;
- int isdir;
- {
- char *cp = cmd;
- opt_t opts;
-
- catname = isdir;
-
- /*
- * Parse options for this target
- */
- opts = strtol(cp, &cp, 8);
- if (*cp++ != ' ') {
- error("settarget: options not delimited");
- return;
- }
- options = opts;
-
- /*
- * Handle target
- */
- if (exptilde(target, cp) == NULL)
- return;
- ptarget = target;
- while (*ptarget)
- ptarget++;
-
- ack();
- }
-
- /*
- * Cleanup in preparation for exiting.
- */
- extern void cleanup()
- {
- /* We don't need to do anything */
- }
-
- /*
- * Server routine to read requests and process them.
- */
- extern void server()
- {
- static char cmdbuf[BUFSIZ];
- register char *cp;
- register int n;
- extern jmp_buf finish_jmpbuf;
-
- if (setjmp(finish_jmpbuf))
- return;
- (void) signal(SIGHUP, sighandler);
- (void) signal(SIGINT, sighandler);
- (void) signal(SIGQUIT, sighandler);
- (void) signal(SIGTERM, sighandler);
- (void) signal(SIGPIPE, sighandler);
- (void) umask(oumask = umask(0));
- (void) strcpy(tempname, _RDIST_TMP);
- if (fromhost) {
- message(MT_SYSLOG, "Startup for %s", fromhost);
- #if defined(SETARGS)
- setproctitle("Serving %s", fromhost);
- #endif /* SETARGS */
- }
-
- /*
- * Let client know we want it to send it's version number
- */
- (void) sendcmd(S_VERSION, NULL);
-
- if (remline(cmdbuf, sizeof(cmdbuf), TRUE) < 0) {
- error("server: expected control record");
- return;
- }
-
- if (cmdbuf[0] != S_VERSION || !isdigit(cmdbuf[1])) {
- error("Expected version command, received: \"%s\".", cmdbuf);
- return;
- }
-
- proto_version = atoi(&cmdbuf[1]);
- if (proto_version != VERSION) {
- error("Protocol version %d is not supported.", proto_version);
- return;
- }
-
- /* Version number is okay */
- ack();
-
- /*
- * Main command loop
- */
- for ( ; ; ) {
- n = remline(cp = cmdbuf, sizeof(cmdbuf), TRUE);
- if (n == -1) /* EOF */
- return;
- if (n == 0) {
- error("server: expected control record");
- continue;
- }
-
- switch (*cp++) {
- case C_SETCONFIG: /* Configuration info */
- setconfig(cp);
- ack();
- continue;
-
- case C_DIRTARGET: /* init target file/directory name */
- settarget(cp, TRUE);
- continue;
-
- case C_TARGET: /* init target file/directory name */
- settarget(cp, FALSE);
- continue;
-
- case C_RECVREG: /* Transfer a regular file. */
- recvit(cp, S_IFREG);
- continue;
-
- case C_RECVDIR: /* Transfer a directory. */
- recvit(cp, S_IFDIR);
- continue;
-
- case C_RECVSYMLINK: /* Transfer symbolic link. */
- recvit(cp, S_IFLNK);
- continue;
-
- case C_RECVHARDLINK: /* Transfer hard link. */
- hardlink(cp);
- continue;
-
- case C_END: /* End of transfer */
- *ptarget = CNULL;
- if (catname <= 0) {
- error("server: too many '%c's", C_END);
- continue;
- }
- ptarget = sptarget[--catname];
- *ptarget = CNULL;
- ack();
- continue;
-
- case C_CLEAN: /* Clean. Cleanup a directory */
- clean(cp);
- continue;
-
- case C_QUERY: /* Query file/directory */
- query(cp);
- continue;
-
- case C_SPECIAL: /* Special. Execute commands */
- dospecial(cp);
- continue;
-
- case C_CMDSPECIAL: /* Cmd Special. Execute commands */
- docmdspecial();
- continue;
-
- #ifdef DOCHMOD
- case C_CHMOD: /* Set mode */
- dochmod(cp);
- continue;
- #endif /* DOCHMOD */
-
- case C_ERRMSG: /* Normal error message */
- if (cp && *cp)
- message(MT_NERROR|MT_NOREMOTE, "%s", cp);
- continue;
-
- case C_FERRMSG: /* Fatal error message */
- if (cp && *cp)
- message(MT_FERROR|MT_NOREMOTE, "%s", cp);
- return;
-
- default:
- error("server: unknown command '%s'", cp - 1);
- case CNULL:
- continue;
- }
- }
- }
-